# encoding: utf-8
"""
brightcontent.plugins.comments

The Bible is: http://www.ietf.org/rfc/rfc4685.txt
"""
from pprint import pprint

import os, cStringIO
from string import Template
from datetime import *#; from dateutil.relativedelta import *
from selector import Selector
from Ft.Lib import Uri
from Ft.Xml.Xslt import Transform
from wsgixml.applyxslt import setup_xslt_params
from paste import httpexceptions
import amara
import posixpath
from pkg_resources import resource_string

from brightcontent.core import SLUGCHARS
from brightcontent.util import UrlDispatch, get_base_url, web_trigger, quick_xml_multiscan, fixup_namespaces
from brightcontent.core.store import flatfile_repository, ATOM10_NS, COMMON_PREFIXES
from paste.wsgiwrappers import WSGIRequest

BC_NS = 'http://brightcontent.net/ns/comments'
ATOM_NS = u'http://www.w3.org/2005/Atom'
COMMENT_FORMS_ENV_VAR = 'brightcontent.comments.forms'
COMMENTS_FOR_ENTRY_VAR = 'brightcontent.comments.for-current-entry'
#XPath expression that can be dynamically invoked by an XSLT processor
#to process the archives requires namespace declarations
#xmlns:f="http://xmlns.4suite.org/ext"
#xmlns:bc="http://brightcontent.net/ns/"
COMMENT_FORMS_XSLT_PARSE_EXPRESSION = u'f:parse-xml($bc:%s)' % COMMENT_FORMS_ENV_VAR
COMMENTS_ENTRIES_XSLT_PARSE_EXPRESSION = u'f:parse-xml($bc:%s)' % COMMENTS_FOR_ENTRY_VAR

from brightcontent.core.store import COMMON_PREFIXES as BASE_COMMON_PREFIXES

COMMON_PREFIXES = BASE_COMMON_PREFIXES.copy()
COMMON_PREFIXES['thr'] = "http://purl.org/syndication/thread/1.0"
CREATE_COMMENT_TRIGGER = '/bctriggers/create-comment?id=%s'

class CommentForms(object):
    def __init__(self, application, store, app_conf):
        self.app = application
        self.entry_store = store
        self.store = CommentStore(**app_conf)
        self.app_conf = app_conf # FIXME: temporary until config middleware is working correctly
        self.s = Selector()
        self.s.parser.patterns['slug'] = r'[%s]+/' % SLUGCHARS
        self.s.add('/{year:digits}/{month:digits}/{day:digits}/{slug:slug}', GET=self.comments, POST=self.add_comment)
        self.s.add('/{year:digits}/{month:digits}/{day:digits}/{slug:any}/comments.atom', GET=self.comments)
        self.s.add('/{:any}', GET=self.app, PUT=self.app, POST=self.app, DELETE=self.app)
        self.s.add('/', GET=self.app, PUT=self.app, POST=self.app, DELETE=self.app)
        

    def __call__(self, environ, start_response):
        print "In comments"
        #from pprint import pprint
        #pprint(environ)
        return self.s(environ, start_response)

    def get_entry_id(self, environ):
        args = environ['wsgiorg.routing_args'][1]
        edate = date(int(args['year']), int(args['month']), int(args['day']))
        return self.entry_store.generate_url(args['slug'], edate)

    def comments(self, environ, start_response):
        # entry_id = self.get_current_entry(environ)
        entry_id = self.get_entry_id(environ)
        if self.entry_store.get_entry_by_id(entry_id):
            #let the rest get a 404
            return self.app(environ, start_response)
        comments = self.get_comments(entry_id)
        if not comments:
            return self.comment_forms(environ, start_response)
        comments_bodies = [ body for u, body in comments ]
        feed_base_url = get_base_url(environ).rstrip('/') + environ['PATH_INFO']
        comment_feed = self.assemble_comments(comments_bodies, entry_id, feed_base_url)
        if environ['PATH_INFO'].endswith('comments.atom'):
            start_response('200 OK', [('content-type', 'application/atom+xml')])
            return [comment_feed]
        environ[COMMENTS_FOR_ENTRY_VAR] = comment_feed
        return self.comment_forms(environ, start_response)

    def get_comments(self, entry_id):
        all_comments = self.store.index
        comments = []
        for atom_updated, atom_published, atom_id, fn in all_comments:
            if atom_id == entry_id:
                comments.append((atom_updated, fn))
        # FIXME add config option for reverse order comment display,
        # which would call for a .reverse()
        if comments:
            comments.sort()
            return comments
        return None

    def add_comment(self, environ, start_response):
        print "in add comments"
        parent_link = get_base_url(environ)
        now = datetime.now()
        req = WSGIRequest(environ)
        info = {}
        info['atom_author_name'] = req.POST.get('name', '').decode('utf-8')
        info['atom_author_uri'] = req.POST.get('uri', '').decode('utf-8')
        info['atom_content'] = req.POST.get('content', '').decode('utf-8')
        print info['atom_content']
        valid = []
        errors = []
        required = ['atom_content', 'atom_author_name']
        for k, v in info.items():
            if 'atom_' in k and v != '':
                valid.append(k)
            else:
                errors.append(k)
        invalid = [ x for x in errors if x in required ]
        if invalid:
            print 'had errors in %s' % invalid
            for v in valid:
                info['%s_error' % v] = ''
            environ['bc.comments.template_args'] = info
            return self.comments(environ, start_response)
        info['atom_title'] = unicode(environ['PATH_INFO'])
        info['parent_id'] = self.get_entry_id(environ)
        #FIXME: we're gonna have to be the ones providing the ID to the store.
        comment_date = datetime.now()
        info['comment_anchor'] = '-C'+str(comment_date.strftime('%Y%m%d-%H-%M-%S'))
        info['atom_id'] = info['parent_id'] + info['comment_anchor']
        # COMMENT_TEMPLATE.safe_substitute(locals())
        xslt = resource_string(__name__, 'comments/create.xslt')
        content = Transform(
            '<?xml version="1.0"?><null />',
            xslt, params=setup_xslt_params(BC_NS, info)
        )
        print content
        comment_slug = environ['wsgiorg.routing_args'][1]['slug']+'-C'+str(now.strftime('%Y%m%d-%H-%M-%S'))
        self.store.create_entry(content, comment_slug)
        # make sure we refresh the index
        self._index = None
        info['bc.comments.created'] = True
        environ['bc.comments.template_args'] = info
        return self.comments(environ, start_response)

    def comment_forms(self, environ, start_response):
        #Setup for the post comment form
        print "in comment-forms"
        template_vars = environ.get('bc.comments.template_args', {})
        environ['brightcontent.comments.parse'] = COMMENTS_ENTRIES_XSLT_PARSE_EXPRESSION
        xslt = resource_string(__name__, 'comments/forms.xslt')
        xml = '<?xml version="1.0"?><none />'
        environ[COMMENT_FORMS_ENV_VAR] = Transform(xml,
                        xslt, params=setup_xslt_params(BC_NS, template_vars))
        environ['brightcontent.comments.forms.parse'] = COMMENT_FORMS_XSLT_PARSE_EXPRESSION
        return self.app(environ, start_response)

    def assemble_comments(self, comments, entry_id, base_url):
        #print "In comments.assemble_comments"
        #print entry_id
        atom = self.comment_feed_template()
        atom.feed.title = u'Comments'
        comments_id = u'%s/comments.atom' % entry_id.rstrip('.atom')
        atom.feed.id = comments_id
        atom.feed.link[0].xml_set_attribute(u'href', comments_id)
        # TODO: replace with amara.binderytools.quick_xml_scan when fixed
        latest_comment = amara.parse(
            Uri.OsPathToUri(comments[0], attemptAbsolute=1),
            prefixes=BASE_COMMON_PREFIXES)
        #feed_updated = str(latest_comment.entry.updated)
        atom.feed.xml_append(latest_comment.entry.updated)
        del latest_comment

        for fn in comments:
            e = amara.parse(Uri.OsPathToUri(fn, attemptAbsolute=1), prefixes=COMMON_PREFIXES)
            atom.feed.xml_append(e.entry)
        fixup_namespaces(atom)
        buffer = cStringIO.StringIO()
        atom.xml(indent=True, stream=buffer)
        return buffer.getvalue()

    def comment_feed_template(self):
        f = amara.create_document(u'feed', ATOM_NS, prefixes=COMMON_PREFIXES)
        f.feed.xml_append(f.xml_create_element(u'title', ATOM_NS))
        f.feed.xml_append(f.xml_create_element(u'id', ATOM_NS))
        # f.feed.xml_append(f.xml_create_element(u'updated', ATOM_NS))
        f.feed.xml_append(
            f.xml_create_element(u'link', ATOM_NS,
                attributes={ u'rel' : u'self',
                             u'title' : u'Comment Feed',
                             u'type' : u'application/atom+xml;type=feed' }
            )
        )
        return f
    

class CommentStore(flatfile_repository):
    def __init__(self, **kw):
        flatfile_repository.__init__(self, **kw)
        self.storedir = kw.get('flatfile_comment_storedir', 'atomcommentstore')

    #On a refactoring pass make the base class indexing class more parameterized, so all this
    #logic can be inherited...DRY
    @property
    def index(self):
        if self._index is not None:
            return self._index
        entries = []
        for root, dirs, files in os.walk(self.storedir):
            for fn in files:
                #if fn.endswith(ENVELOPE_XML_FILE): continue
                fn = os.path.join(root, fn)
                details = quick_xml_multiscan(
                    fn, 'atom:id|thr:in-reply-to|atom:updated',
                    display='.|@ref', count=4, prefixes=COMMON_PREFIXES)
                #Would be nice to find a more elegant way
                # print details
                values = {}
                for ((ns, local), value) in details:
                    exec "atom_%s = u'%s'"%(local.decode('utf-8').replace('-', ''), value.decode('utf-8'))
                atom_updated = amara.binderytools.parse_isodate(atom_updated)
                entries.append((atom_updated, atom_id, atom_inreplyto, fn))
        entries.sort()
        entries.reverse()
        self._index = entries
        return entries

